using System;
using System.Collections.Generic;
using System.Reflection;
#if TEST
using Fadd.Commands.Tests;
using Xunit;
#endif

namespace Fadd.Commands
{
    /// <summary>
    /// This class is used to receive and handle 
    /// </summary>
    public class ObjectProxyReceiver
    {
        private readonly Dictionary<Type, object> _instances = new Dictionary<Type, object>();

        /// <summary>
        /// Event used to retrieve an instace for a specific type.
        /// </summary>
        /// <remarks>
        /// The instance is used to invoke the requested method.
        /// </remarks>
        public event InstanceRequestedHandler InstanceRequested = delegate { };

        /// <summary>
        /// Initializes a new instance of the <see cref="ObjectProxyReceiver"/> class.
        /// </summary>
        /// <param name="dispatcher">Dispatcher that the proxy commands will arrive on.</param>
        public ObjectProxyReceiver(ICommandDispatcher dispatcher)
        {
            dispatcher.Add(typeof(ObjectProxyCommand), OnProxyRequest);
        }

        /// <summary>
        /// Type to instance mappings
        /// </summary>
        /// <remarks>
        /// Maps all types to object instances that are used to invoke the method calls.
        /// </remarks>
        public Dictionary<Type, object> Instances
        {
            get { return _instances; }
        }

        /// <summary>
        /// Called when a proxy command arrives.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        private bool OnProxyRequest(object source, CommandEventArgs args)
        {
            ObjectProxyCommand cmd = (ObjectProxyCommand) args.Command;

            object instance;
            lock (Instances)
            {
                if (Instances.ContainsKey(cmd.Type))
                    instance = Instances[cmd.Type];
                else
                {
                    InstanceRequestedEventArgs myArgs = new InstanceRequestedEventArgs(cmd.Type);
                    InstanceRequested(this, myArgs);
                    if (myArgs.Instance == null)
                        throw new InvalidOperationException("Failed to find instance for type: " + cmd.Type.FullName);

                    Instances[cmd.Type] = myArgs.Instance;
                    instance = myArgs.Instance;
                }
            }

            try
            {
                cmd.ReturnValue = cmd.Method.Invoke(instance, cmd.Args);
            }
            catch(TargetInvocationException err)
            {
                cmd.Exception = err.InnerException;
            }

            return true;
        }

        #if TEST
        [Fact]
        private static void TestReceiver()
        {
            CommandManager mgr = new CommandManager();

            // Faking server side.
            ObjectProxyReceiver receiver = new ObjectProxyReceiver(mgr);
            receiver.InstanceRequested += delegate(object source, InstanceRequestedEventArgs args)
                                              {
                                                  args.Instance = new TestObject();
                                              };
            

            // Faking client side.
            ObjectProxyGenerator generator = new ObjectProxyGenerator(mgr);
            ITestObject obj = (ITestObject) generator.Generate(typeof (ITestObject));

            // Exceptions should be transported.
            Assert.Throws(typeof (InvalidOperationException), delegate { obj.DoThis(); });

            // Check value.
            string value = obj.TestIt("Something", 5);
            Assert.Equal("5hihi", value);
        }
        #endif
    }
    /// <summary>
    /// Arguments for <see cref="InstanceRequestedHandler"/>
    /// </summary>
    public class InstanceRequestedEventArgs : EventArgs
    {
        private readonly Type _type;
        private object _instance;


        /// <summary>
        /// Initializes a new instance of the <see cref="InstanceRequestedEventArgs"/> class.
        /// </summary>
        /// <param name="type">Type of instance wanted.</param>
        public InstanceRequestedEventArgs(Type type)
        {
            Check.Require(type, "type");
            _type = type;
        }

        /// <summary>
        /// Type of instance wanted.
        /// </summary>
        public Type Type
        {
            get { return _type; }
        }

        /// <summary>
        /// Instance that will be used to invoke method.
        /// </summary>
        public object Instance
        {
            get { return _instance; }
            set { _instance = value; }
        }
    }

    /// <summary>
    /// Delegate used by <see cref="InstanceRequestedHandler"/> to find instance to invoke on.
    /// </summary>
    /// <param name="source"><see cref="InstanceRequestedHandler"/></param>
    /// <param name="args">Arguments</param>
    public delegate void InstanceRequestedHandler(object source, InstanceRequestedEventArgs args);
}
